using System;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;

/*	-----------------------------------------------------------------------	
	Copyright:	umlungu consulting (Pty) ltd
	Author:		Alan Benington	
	Started:	2005-05-15	
	Status:		release	
	Version:	2.0.0
	Build:		20080113
	License:	GNU General Public License
	-----------------------------------------------------------------------	*/

/*	-----------------------------------------------------------------------	
	Development Notes:
	==================
	20070524:	Modification for .Net 2.0
	20071022:	Added Add overloads to add image blogs
	20080113:	Extended to include the following:
				- Methods for html blogs + xml (Add, edit)
				- Image list (duplication + extension of comments)
	---------------------------------------------------------------------------	*/

namespace umlungu.engineX.blogX {
	/// <summary>
	/// umlungu engineX blog blog class.
	/// </summary>
	public class x_blog {
		#region Invisible properties
		private HttpApplicationState _application;
		private x_blogspace _blogspace;
		private string _path;
		private XmlDocument _blogs, _config;
		private XmlDocument _root;
		#endregion

		#region Visible properties
		private XmlElement blog;
		/// <summary>The blog element itself</summary>
		/// <value>An XmlElement defining the blog</value>
		public XmlElement Blog	{
			get	{	return blog;	}	
			set	{	blog = value;	}
		}
		/// <summary>The blog's XmlDocument</summary>
		/// <value>An XmlDocument object</value>
		public XmlDocument Document	{
			get	{	return _blogs;	}	
		}
		/// <summary>The blogs' document (ie the topic document)</summary>
		/// <value>The DocumentElement for the blog topic</value>
		public XmlNode Blogs {	
			get	{	return _blogs.DocumentElement;	}	
		}
		private string _name;
		/// <summary>The blog's blogspace</summary>
		/// <value>The name of the blog's blogspace</value>
		public string Name {
			get	{	return _name;	}
		}
		private string _topic;
		/// <summary>The blog's topic</summary>
		/// <value>The name of the blog's topic</value>
		public string Topic	{	
			get	{	return _topic;	}	
		}
		private string _id;
		/// <summary>The blog's identifier</summary>
		/// <value>A string identifier for the blog</value>
		public string ID {	
			get	{	return _id;	}	
		}
		private string _commentid;
		/// <summary>The blog comment's identifier</summary>
		/// <value>A string identifier for the blog's comment</value>
		public string CommentID {	
			get	{	return _commentid;	}	
		}
		private string _imageid;
		/// <summary>The blog image's identifier</summary>
		/// <value>A string identifier for the blog's image</value>
		public string ImageID {
			get { return _imageid; }
		}
		/// <summary>The element that contains the list of blogs</summary>
		/// <value>An XmlElement containing the blog elements</value>
		public XmlElement BlogList {
			get	{	return (XmlElement) _blogs.SelectSingleNode("//blogs");	}	
		}
		/// <summary>The blogs definition document</summary>
		/// <value>The DocumentElement for the blog defintion file (../data/blogx.xml)</value>
		public XmlNode Root	{
			get	{	return _root.DocumentElement;	}	
		}
		/// <summary>The creation date of the blog topic</summary>
		/// <value>A DateTime object containg the blog topic creation date</value>
		public DateTime Date	{	
			get	{	
				XmlElement blogsel = (XmlElement) _blogs.SelectSingleNode("//blogs");
				string blogsdt = blogsel.GetAttribute("date");
				return DateTime.Parse(blogsdt, DateTimeFormatInfo.InvariantInfo);
			}
		}
		/// <summary>The type of blogs contained in the blog topic</summary>
		/// <value>A string indicating the type of blogs in the topic (default is 'text')</value>
		public string Type	{	
			get	{	
				XmlElement blogsel = (XmlElement) _blogs.SelectSingleNode("//blogs");
				string blogstype = blogsel.GetAttribute("type");
				return (blogstype != "")? blogstype : "text";
			}
			set {
				XmlElement blogsel = (XmlElement) _blogs.SelectSingleNode("//blogs");
				blogsel.SetAttribute("type", value);
			}
		}
		#endregion

		#region Constructors/Destructors
		/// <overloads>Constructor</overloads>
		/// <summary>Default constructor</summary>
		public x_blog() {	// an blank instance
			getBlogs();
		}
		/// <summary>Constructor</summary>
		/// <param name="application">The web application object</param>
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		public x_blog(HttpApplicationState application, string name, string topic) {
			initialise(application);
			_Load(name, topic);
		}
		/// <summary>Constructor</summary>
		/// <param name="application">The web application object</param>
		public x_blog(HttpApplicationState application) {
			initialise(application);
		}
		/// <summary>Constructor</summary>
		/// <param name="path">Path to an instance from a file (in an arbitary path)</param>
		public x_blog(string path) {
			getBlogs(path);
		}
		/// <summary>Default destructor</summary>
		~x_blog() {
			if (blog != null && _blogs != null) {
				if (_blogs.Equals(blog.ParentNode))
					_blogs.RemoveChild(blog);
			}
		}
		#endregion

		#region Public methods
		/// <overloads>Get a blog. Loads the blogspace/blogtopic and selects the relevant blog into the blog element ('Blog' property)</overloads>
		/// <summary>Get a blog.</summary>
		/// <param name="id">Blog identifier</param>
		public void Get(int id) {	// assumes the blog loaded
			selectBlog(id);
		}
		/// <summary>Get a blog.</summary>
		/// <param name="id">Blog identifier</param>
		public void Get(string id) {
			selectBlog(id);
		}
		/// <summary>Get a blog.</summary>
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		public void Get(string name, string topic) {
			_Load(name, topic);
		}
		/// <summary>Get a blog.</summary>
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		/// <param name="id">Blog identifier</param>
		public void Get(string name, string topic, int id) {
			_Load(name, topic);
			selectBlog(id);
		}
		/// <summary>Get a blog.</summary>
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		/// <param name="id">Blog identifier</param>
		public void Get(string name, string topic, string id) {
			_Load(name, topic);
			selectBlog(id);
		}
		/// <summary>Get a blog.</summary>
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		/// <param name="which">Blog archive</param>
		/// <param name="id">Blog identifier</param>
		public void Get(string name, string topic, string which, int id) {
			_Load(name, topic, which);
			selectBlog(id);
		}
		/// <summary>Get a blog.</summary>
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		/// <param name="which">Blog archive</param>
		/// <param name="id">Blog identifier</param>
		public void Get(string name, string topic, string which, string id) {
			_Load(name, topic, which);
			selectBlog(id);
		}
		/// <summary>Get a blog.</summary>
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		/// <param name="id">Blog identifier</param>
		/// <param name="topics">Flag for topics list</param>
		/// <param name="archives">Flag for archives list</param>
		public void Get(string name, string topic, int id, bool topics, bool archives) {
			_Load(name, topic);
			selectBlog(id);
			if (topics || archives) removeBlogs(id);
			if (topics) addTopics();
			if (archives) addArchives();
		}
		/// <summary>Get a blog.</summary>
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		/// <param name="which">Blog archive</param>
		/// <param name="id">Blog identifier</param>
		/// <param name="topics">Flag for topics list</param>
		/// <param name="archives">Flag for archives list</param>
		public void Get(string name, string topic, string which, int id, bool topics, bool archives) {
			_Load(name, topic, which);
			selectBlog(id);
			if (topics || archives) removeBlogs(id);
			if (topics) addTopics();
			if (archives) addArchives();
		}

		/// <summary>
		/// Gets multiple blogs.
		/// </summary>
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		/// <param name="ids">Blog identifiers</param>
		public void Gets(string name, string topic, string ids) {
			_Load(name, topic);
			removeBlogs(ids);
		}
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		/// <param name="which">Blog archive</param>
		/// <param name="ids">Blog identifiers</param>
		public void Gets(string name, string topic, string which, string ids) {
			_Load(name, topic, which);
			removeBlogs(ids);
		}
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		/// <param name="ids">Blog identifiers</param>
		/// <param name="topics">Flag for topics list</param>
		/// <param name="archives">Flag for archives list</param>
		public void Gets(string name, string topic, string ids, bool topics, bool archives) {
			_Load(name, topic);
			removeBlogs(ids);
			if (topics) addTopics();
			if (archives) addArchives();
		}
		/// <param name="name">Blogspace name</param>
		/// <param name="topic">Blogtopic name</param>
		/// <param name="which">Blog archive</param>
		/// <param name="ids">Blog identifiers</param>
		/// <param name="topics">Flag for topics list</param>
		/// <param name="archives">Flag for archives list</param>
		public void Gets(string name, string topic, string which, string ids, bool topics, bool archives) {
			_Load(name, topic, which);
			removeBlogs(ids);
			if (topics) addTopics();
			if (archives) addArchives();
		}
		
		/// <overloads>Add a new blog to a space+topic.</overloads>
		/// <summary>Default blog format, ID is automatically assigned</summary>
		/// <param name="name">The blog's blogspace</param>
		/// <param name="topic">The blog's topic</param>
		/// <param name="author">The author of the blog</param>
		/// <param name="title">The title of the blog</param>
		/// <param name="text">The text string of the blog</param>
		/// <param name="ishtml">Flag to indicate if text is html (or plain text)</param>
		public void Add(string name, string topic, string author, string title, string text, bool ishtml) {
			Add(name, topic, author, null, title, text, ishtml);
		}
		/// <summary>Default blog format, ID is specified</summary>
		/// <param name="name">The blog's blogspace</param>
		/// <param name="topic">The blog's topic</param>
		/// <param name="author">The author of the blog</param>
		/// <param name="id">The identifier of the blog</param>
		/// <param name="title">The title of the blog</param>
		/// <param name="text">The text string of the blog</param>
		/// <param name="ishtml">Flag to indicate if text is html (or plain text)</param>
		public void Add(string name, string topic, string author, string id, string title, string text, bool ishtml) {
			XmlElement newblog = newBlog(name, topic, author, id, title);
			if (ishtml) {	// Create a CData section to hold this so not to worry about bad xhtml.
				newblog.AppendChild(addHtml(text));
			} else {
				newblog.AppendChild(addText(text));
			}
			addBlog(name, topic, newblog);
		}
		/// <summary>Default blog format, ID is specified</summary>
		/// <param name="name">The blog's blogspace</param>
		/// <param name="topic">The blog's topic</param>
		/// <param name="author">The author of the blog</param>
		/// <param name="id">The identifier of the blog</param>
		/// <param name="title">The title of the blog</param>
		/// <param name="text">The text string of the blog</param>
		/// <param name="ishtml">Flag to indicate if text is html (or plain text)</param>
		/// <param name="xml">The xml of the blog</param>
		public void Add(string name, string topic, string author, string title, string text, bool ishtml, string xml) {
			Add(name, topic, author, null, title, text, ishtml, xml);
		}
		/// <summary>Default blog format, ID is specified</summary>
		/// <param name="name">The blog's blogspace</param>
		/// <param name="topic">The blog's topic</param>
		/// <param name="author">The author of the blog</param>
		/// <param name="id">The identifier of the blog</param>
		/// <param name="title">The title of the blog</param>
		/// <param name="text">The text string of the blog</param>
		/// <param name="ishtml">Flag to indicate if text is html (or plain text)</param>
		/// <param name="xml">The xml of the blog</param>
		public void Add(string name, string topic, string author, string id, string title, string text, bool ishtml, string xml) {
			XmlElement newblog = newBlog(name, topic, author, id, title);
			if (ishtml) {	// Create a CData section to hold this so not to worry about bad xhtml.
				newblog.AppendChild(addHtml(text));
			} else {
				newblog.AppendChild(addText(text));
			}
			newblog.InnerXml = newblog.InnerXml + xml;
			addBlog(name, topic, newblog);
		}
		/// <summary>Add a blog with a date</summary>
		/// <param name="name">The blog's blogspace</param>
		/// <param name="topic">The blog's topic</param>
		/// <param name="author">The author of the blog</param>
		/// <param name="yyyy">The year of the blog's date</param>
		/// <param name="mm">The month of the blog's date</param>
		/// <param name="dd">The day of the blog's date</param>
		/// <param name="id">The identifier of the blog</param>
		/// <param name="title">The title of the blog</param>
		/// <param name="text">The text string of the blog</param>
		/// <param name="ishtml">Flag to indicate if text is html (or plain text)</param>
		public void Add(string name, string topic, string author, string yyyy, string mm, string dd, string id, string title, string text, bool ishtml) {
			DateTime date = newDate(yyyy, mm, dd);
			XmlElement newblog = newBlog(name, topic, author, date, id, title);
			if (ishtml) {	// Create a CData section to hold this so not to worry about bad xhtml.
				newblog.AppendChild(addHtml(text));
			} else {
				newblog.AppendChild(addText(text));
			}
			addBlog(name, topic, newblog);
		}
		/// <summary>Add a blog with a date and a summary text field</summary>
		/// <param name="name">The blog's blogspace</param>
		/// <param name="topic">The blog's topic</param>
		/// <param name="author">The author of the blog</param>
		/// <param name="yyyy">The year of the blog's date</param>
		/// <param name="mm">The month of the blog's date</param>
		/// <param name="dd">The day of the blog's date</param>
		/// <param name="id">The identifier of the blog</param>
		/// <param name="title">The title of the blog</param>
		/// <param name="txt">The summary text string of the blog</param>
		/// <param name="text">The text string of the blog</param>
		/// <param name="ishtml">Flag to indicate if text is html (or plain text)</param>
		public void Add(string name, string topic, string author, string yyyy, string mm, string dd, string id, string title, string txt, string text, bool ishtml) {
			DateTime date = newDate(yyyy, mm, dd);
			XmlElement newblog = newBlog(name, topic, author, date, id, title);
			newblog.AppendChild(addText(txt));	// add plain text section, typically summary.
			if (ishtml) {	// Create a CData section to hold this so not to worry about bad xhtml.
				newblog.AppendChild(addHtml(text));
			} else {
				newblog.AppendChild(addText(text));
			}
			addBlog(name, topic, newblog);
		}
		/// <summary>Add a blog from an xml document</summary>
		/// <param name="doc">The xml document</param>
		/// <param name="parent">The parent node</param>
		/// <param name="author">The author of the blog</param>
		/// <param name="title">The title of the blog</param>
		/// <param name="image">The image associated with the blog</param>
		/// <param name="html">The html string of the blog</param>
		public void Add(XmlDocument doc, XmlNode parent, string author, string title, string image, string html) {
			XmlElement newblog = newBlog(doc, author, title);
			newblog.AppendChild(addHtml(doc, html));
			if (image != null && image != "")
				newblog.AppendChild(addImage(doc, image));
			parent.PrependChild(newblog);	// make the first node (used in topic/blogspace blog)
		}
		/// <param name="path">Brief description of the parameter</param>
		/// <param name="author">The author of the blog</param>
		/// <param name="title">The title of the blog</param>
		/// <param name="html">The html string of the blog</param>
		public void Add(string path, string author, string title, string html) {
			XmlElement newblog = newBlog(author, title);
			newblog.AppendChild(addHtml(html));
			// Add blog entry and save to arbitary path
			addBlog(path, newblog);
		}
		/// <param name="name">The blog's blogspace</param>
		/// <param name="topic">The blog's topic</param>
		/// <param name="author">The author of the blog</param>
		/// <param name="title">The title of the blog</param>
		/// <param name="xml">The xml of the blog</param>
		public void Add(string name, string topic, string author, string title, string xml) {
			XmlElement newblog = newBlog(name, topic, author, title);
			newblog.InnerXml = newblog.InnerXml + xml;
			addBlog(name, topic, newblog);
		}
		/// <summary>Image blog format, ID is automatically assigned</summary>
		/// <param name="name">The blog's blogspace</param>
		/// <param name="topic">The blog's topic</param>
		/// <param name="author">The author of the blog</param>
		/// <param name="title">The title of the blog</param>
		/// <param name="text">The text string of the blog</param>
		/// <param name="ishtml">Flag to indicate if text is html (or plain text)</param>
		/// <param name="image">The image filename</param>
		public void AddImage(string name, string topic, string author, string title, string text, bool ishtml, string image) {
			AddImage(name, topic, author, null, title, text, ishtml, image);
		}
		/// <summary>Image blog format, ID is specified</summary>
		/// <param name="name">The blog's blogspace</param>
		/// <param name="topic">The blog's topic</param>
		/// <param name="author">The author of the blog</param>
		/// <param name="id">The identifier of the blog</param>
		/// <param name="title">The title of the blog</param>
		/// <param name="text">The text string of the blog</param>
		/// <param name="ishtml">Flag to indicate if text is html (or plain text)</param>
		/// <param name="image">The image filename</param>
		public void AddImage(string name, string topic, string author, string id, string title, string text, bool ishtml, string image) {
			XmlElement newblog = newBlog(name, topic, author, id, title);
			if (ishtml) {	// Create a CData section to hold this so not to worry about bad xhtml.
				newblog.AppendChild(addHtml(text));
			} else {
				newblog.AppendChild(addText(text));
			}
			if (image != null && image != "")
				newblog.AppendChild(addImage(image));
			addBlog(name, topic, newblog);
		}
		/// <overloads>Adds a blog by adding a CData to hold (encoded) html</overloads>
		/// <summary>Add to current blog document</summary>
		private XmlElement addHtml(string html) {	
			return(addHtml(_blogs, html, null));
		}
		/// <summary>Add to any xml document</summary>
		private XmlElement addHtml(XmlDocument doc, string html) {
			return(addHtml(doc, html, null));
		}
		/// <summary>Add to any xml document and assign a name attribute</summary>
		private XmlElement addHtml(XmlDocument doc, string html, string name) {
			XmlElement el = doc.CreateElement("bloghtml");
			XmlCDataSection CData = doc.CreateCDataSection(htmlIn(html));
			el.AppendChild(CData);
			if (name != null && name != "")
				el.SetAttribute("name", name);
			return(el);
		}
		/// <overloads>Adds a blog by adding a 'blogtext' element to hold the text</overloads>
		/// <summary>Add to current blog document</summary>
		private XmlElement addText(string text) {
			return(addText(_blogs, textIn(text), null));
		}
		/// <summary>Add to any xml document</summary>
		private XmlElement addText(XmlDocument doc, string text) {
			return(addText(doc, textIn(text), null));
		}
		/// <summary>Add to any xml document and assign a name attribute</summary>
		private XmlElement addText(XmlDocument doc, string text, string name) {
			XmlElement el = doc.CreateElement("blogtext");
			el.InnerXml = textIn(text);
			if (name != null && name != "")
				el.SetAttribute("name", name);
			return(el);
		}
		private XmlElement addImage(string name) {
			return(addImage(_blogs, name));
		}
		private XmlElement addImage(XmlDocument doc, string name) {
			XmlElement el = doc.CreateElement("image");
			el.InnerXml = name;
			return(el);
		}
		/// <overloads>Create a new blog</overloads>
		/// <summary>Create with author, title (auto id)</summary>
		private XmlElement newBlog(string author, string title) {
			return newBlog(_blogs, author, title, null);
		}
		/// <summary>Create with author, title, id</summary>
		private XmlElement newBlog(string author, string id, string title) {
			return newBlog(_blogs, author, title, id);
		}
		/// <summary>Create on any doc</summary>
		private XmlElement newBlog(XmlDocument doc, string author, string title) {
			return newBlog(doc, author, title, null);
		}
		/// <summary>Create on any doc</summary>
		private XmlElement newBlog(XmlDocument doc, string author, string title, string id) {
			XmlElement thisblog = doc.CreateElement("blog");
			string thisdate = setDate(thisblog); // set date attribute
			return newblog(thisblog, author, title, id, thisdate);
		}
		/// <summary>Create on any doc</summary>
		private XmlElement newBlog(XmlDocument doc, string author, string title, string id, DateTime date) {
			XmlElement thisblog = doc.CreateElement("blog");
			string thisdate = setDate(thisblog, date); // set date attribute
			return newblog(thisblog, author, title, id, thisdate);
		}
		private XmlElement newBlog(string name, string topic, string author, string title) {
			return newBlog(name, topic, author, null, title);
		}
		private XmlElement newBlog(string name, string topic, string author, string id, string title) {
			initBlogspace(name, topic);	// should check archive
			_Load(name, topic);
			return newBlog(_blogs, author, title, id);
		}
		private XmlElement newBlog(string name, string topic, string author, DateTime date, string id, string title) {
			initBlogspace(name, topic);	// should check archive
			_Load(name, topic);
			return newBlog(_blogs, author, title, id, date);
		}
		private XmlElement newblog(XmlElement newblog, string author, string title, string id, string date) {
			// Add date, author, title and text
			title = title.Replace("%20", " ");
			newblog.InnerXml = String.Concat("<date create='", newblog.GetAttribute("date"), "'>", date, "</date><author>", author, "</author><title>", title, "</title>");
			//newblog.InnerXml = String.Concat("<date>", date, "</date><author>", author, "</author><title>", title, "</title>");
			newblog.SetAttribute("id", getBlogID(id));
			return newblog;
		}
		// Append new blog to end of blog list and save
		private void addBlog(string path, XmlElement newblog) {
			blog = (XmlElement) _blogs.SelectSingleNode("//blogs").AppendChild(newblog);
			_blogs.Save(path);
		}
		private void addBlog(string name, string topic, XmlElement newblog) {
			blog = (XmlElement) _blogs.SelectSingleNode("//blogs").AppendChild(newblog);
			_blogs.Save(pathBlog(name, topic));
			// updateRecent(newblog, topic);
			updateSpace(name, topic);
		}
		private string getBlogID(string id) {
			if (id == null || id == "")
				return(getBlogID());
			else {
				if (_blogs.SelectSingleNode(String.Concat("//blogs/blog[@id='", id, "']")) != null)
					throw(new x_exception("error_idexist", "blog id already exists"));
			}
			return(id);
		}
		private string getBlogID() {
			// set id attribute from last id in blog
			XmlElement lastblog = (XmlElement) _blogs.SelectSingleNode("//blogs/blog[last()]");
			int lastid = (lastblog != null)? System.Convert.ToInt32(lastblog.GetAttribute("id")) + 1 : 1;
			return(lastid.ToString());
		}
		/// <overloads>Delete blog.</overloads>
		/// <summary>Identifier as int</summary>
		public void Delete(string name, string topic, int id) {
			Delete(name, topic, id.ToString());
		}
		/// <summary>Identifier as string</summary>
		public void Delete(string name, string topic, string id) {
			_Load(name, topic);
			selectBlog(id);
			blog.ParentNode.RemoveChild(blog);
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}
		/// <overloads>Update existing blog.</overloads>
		/// <summary>Update</summary>
		public void Update(string name, string topic, int id, string title, string txt, string text) {
			Update(name, topic, id.ToString(), title, txt, text);
		}
		/// <summary>Update</summary>
		public void Update(string name, string topic, string id, string title, string txt, string text) {
			_Load(name, topic);
			selectBlog(id);
			setDate(blog);
			updateTitle(title);
			updateText(txt);
			updateHtml(text);
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}
		/// <summary>Update</summary>
		public void Update(string name, string topic, int id, string title, string text, bool ishtml) {
			Update(name, topic, id.ToString(), title, text, ishtml);
		}
		/// <summary>Update</summary>
		public void Update(string name, string topic, string id, string title, string text, bool ishtml) {
			_Load(name, topic);
			selectBlog(id);
			setDate(blog);
			setCreateDate(blog);	// backward compatibility - see summary on method
			updateTitle(title);
			if (ishtml)
				updateHtml(text);
			else
				updateText(text);
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}
		/// <summary>Update</summary>
		public void Update(string name, string topic, int id, string title, string xml) {
			Update(name, topic, id.ToString(), title, "", true, xml);
		}
		/// <summary>Update</summary>
		public void Update(string name, string topic, string id, string title, string xml) {
			Update(name, topic, id.ToString(), title, "", true, xml);
		}
		/// <summary>Update</summary>
		public void Update(string name, string topic, string id, string title, string text, bool ishtml, string xml) {
			//string reserved = "date|author|title";
			_Load(name, topic);
			selectBlog(id);
			setDate(blog);
			setCreateDate(blog);	// backward compatibility - see summary on method
			updateTitle(title);
			if (text != "") {
				if (ishtml)
					updateHtml(text);
				else
					updateText(text);
			}
			try {
				XmlDocument blogxmldoc = new XmlDocument();
				blogxmldoc.LoadXml(String.Format("<blogxml>{0}</blogxml>", xml));
				foreach (XmlNode xmlchld in blogxmldoc.SelectSingleNode("//blogxml").ChildNodes) {
					XmlNode chld = blog.SelectSingleNode(xmlchld.Name);
					if (chld == null) {
						blog.AppendChild(_blogs.ImportNode(xmlchld, true));
					} else {
						chld.InnerXml = xmlchld.InnerXml;
					}
				}
			} catch {
				throw (new x_exception("error_blogxml", "error updating xml blog xml."));
			}
			// Save to blog file
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}
		private void updateTitle(string title) {
			blog.SelectSingleNode("title").InnerText = title.Replace("%20", " ");
		}
		private void updateText(string text) {
			updateText((XmlElement)blog.SelectSingleNode("blogtext"), text);
		}
		private void updateText(XmlElement blogel, string text) {
			if (blogel != null) {
				blogel.RemoveAll();
				blogel.InnerXml = textIn(text);	// this will throw an error if html tags entered incorrectly - this is good (else handle it in a CDATA, but have then to worry about bad html (let alone bad xhtml))
			}
		}
		private void updateHtml(string text) {
			updateHtml((XmlElement)blog.SelectSingleNode("bloghtml"), text);
		}
		private void updateHtml(XmlElement blogel, string text) {
			if (blogel != null) {
				blogel.RemoveAll();
				blogel.AppendChild(_blogs.CreateCDataSection(htmlIn(text)));	// CData section to hold this so not to worry about bad xhtml (although this is assumed at least good html).
			}
		}
		/// <overloads>Lists blogs (in a BlogSpace) with a list of archives.</overloads>
		/// <summary>Current topic</summary>
		public void List(string name, string topic, string which) {
			_Load(name, topic, which);
			addTopics();
			addArchives();
		}
		/// <summary>Topic and archive</summary>
		public void List(string name, string topic) {
			_Load(name, topic);
			addTopics();
			addArchives();
		}
		
		/// <summary>
		/// Saves the current blog to a (new) file.
		/// </summary>
		public string Archive(string archivetype, bool force) {
			if (checkArchive(archivetype) || force) {
				string archname = getArchiveName(archivetype);
				_blogs.Save(pathArchive(_name, _topic, archname));
				return(archname);
			} else {
				return("");
			}
		}

		/// <overloads>Add a comment to a blog.</overloads>		
		/// <summary>Topic and archive</summary>
		public void AddComment(string name, string topic, string which, int id, string username, string author, string title, string text) {
			_Load(name, topic, which);
			_AddComment(name, topic, id, username, author, title, textIn(text), true);
			_blogs.Save(pathArchive(name, topic, which));
		}
		/// <summary>Current topic</summary>
		public void AddComment(string name, string topic, int id, string username, string author, string title, string text) {
			_AddComment(name, topic, id, username, author, title, text, true);
		}
		/// <summary>with approval flag</summary>
		public void AddComment(string name, string topic, int id, string username, string author, string title, string text, bool approve) {
			_Load(name, topic);
			_AddComment(name, topic, id, username, author, title, textIn(text), approve);
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}
		/// <summary>with approval flag</summary>
		protected void _AddComment(string name, string topic, int id, string username, string author, string title, string text, bool approve) {
			selectBlog(id);
			XmlElement cmtsnode = (XmlElement) blog.SelectSingleNode("comments");
			if (cmtsnode == null)
				cmtsnode = (XmlElement) blog.AppendChild(_blogs.CreateElement("comments"));
			// create new comment	
			XmlElement cmtnode = _blogs.CreateElement("comment");
			// set id attribute from last id in blog
			XmlElement lastblog = (XmlElement) blog.SelectSingleNode("comments/comment[last()]");
			int lastid = (lastblog != null)? System.Convert.ToInt32(lastblog.GetAttribute("id")) + 1 : 1;
			_commentid = lastid.ToString();
			cmtnode.SetAttribute("id", _commentid);
			cmtnode.SetAttribute("ok", (approve)? "yes" : "no");
			// set date attribute and descriptive date element
			string date = setDate(cmtnode);
			// Add date, author, title and text
			title = title.Replace("%20", " ");
			text = textIn(text, true);
			cmtnode.InnerXml = String.Concat("<date>", date, "</date><author>", author, "</author><title>", title, "</title><text>", text, "</text>");
			cmtsnode.AppendChild(cmtnode);
		}
		
		/// <overloads>Approve a comment to a blog (ie. set "ok" attribute to "yes").</overloads>
		/// <summary>ids as strings</summary>
		public void ApproveComment(string name, string topic, string id, string comment_id) {
			int blog_id = Convert.ToInt32(id);
			int blog_cid = Convert.ToInt32(comment_id);
			ApproveComment(name, topic, blog_id, blog_cid);
		}
		/// <summary>ids as integers</summary>
		public void ApproveComment(string name, string topic, int id, int comment_id) {
			_Load(name, topic);
			selectBlog(id);
			XmlElement cmtnode = blog.SelectSingleNode("comments/comment[@id='"+comment_id+"']") as XmlElement;
			if (cmtnode != null) 
				cmtnode.SetAttribute("ok", "yes");
			else
				throw(new x_exception("error_commentexist", "comment does not exist"));
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}
		
		/// <overloads>Delete a comment to a blog.</overloads>
		/// <summary>ids as strings</summary>
		public void DeleteComment(string name, string topic, string id, string comment_id) {
			int blog_id = Convert.ToInt32(id);
			int blog_cid = Convert.ToInt32(comment_id);
			DeleteComment(name, topic, blog_id, blog_cid);
		}
		/// <summary>ids as integers</summary>
		public void DeleteComment(string name, string topic, int id, int comment_id) {
			_Load(name, topic);
			selectBlog(id);
			XmlNode cmtnode = blog.SelectSingleNode("comments/comment[@id='"+comment_id+"']");
			if (cmtnode != null) 
				cmtnode.ParentNode.RemoveChild(cmtnode);
			else
				throw(new x_exception("error_commentexist", "comment does not exist"));
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}
		
		/// <summary>
		/// Edit a comment to a blog.
		/// </summary>
		public void EditComment(string name, string topic, int id, int comment_id, string title, string text) {
			_Load(name, topic);
			selectBlog(id);
			XmlElement cmtnode = (XmlElement) blog.SelectSingleNode("comments/comment[@id='"+comment_id+"']");
			if (cmtnode == null) 
				throw(new x_exception("error_commentexist", "comment does not exist"));
			// set date attribute and descriptive date element
			string date = setDate(cmtnode);
			// Add title and text
			cmtnode.SelectSingleNode("date").InnerXml = date;
			cmtnode.SelectSingleNode("title").InnerXml = title.Replace("%20", " ");;
			cmtnode.SelectSingleNode("text").InnerXml = textIn(text);
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}


		/// <overloads>Add a image to a blog.</overloads>		
		/// <summary>Topic and archive</summary>
		public void AddImage(string name, string topic, string which, int id, string username, string author, string title, string text) {
			_Load(name, topic, which);
			_AddImage(name, topic, id, username, author, title, textIn(text), true);
			_blogs.Save(pathArchive(name, topic, which));
		}
		/// <summary>Current topic</summary>
		public void AddImage(string name, string topic, int id, string username, string author, string title, string text) {
			_AddImage(name, topic, id, username, author, title, text, true);
		}
		/// <summary>with approval flag</summary>
		public void AddImage(string name, string topic, int id, string username, string author, string title, string text, bool approve) {
			_Load(name, topic);
			_AddImage(name, topic, id, username, author, title, textIn(text), approve);
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}
		/// <summary>with approval flag</summary>
		protected void _AddImage(string name, string topic, int id, string username, string author, string title, string text, bool approve) {
			selectBlog(id);
			XmlElement imgsnode = (XmlElement)blog.SelectSingleNode("images");
			if (imgsnode == null)
				imgsnode = (XmlElement)blog.AppendChild(_blogs.CreateElement("images"));
			// create new image	
			XmlElement imgnode = _blogs.CreateElement("image");
			// set id attribute from last id in blog
			XmlElement lastblog = (XmlElement)blog.SelectSingleNode("images/image[last()]");
			int lastid = (lastblog != null) ? System.Convert.ToInt32(lastblog.GetAttribute("id")) + 1 : 1;
			_imageid = lastid.ToString();
			imgnode.SetAttribute("id", _imageid);
			imgnode.SetAttribute("ok", (approve) ? "yes" : "no");
			// set date attribute and descriptive date element
			string date = setDate(imgnode);
			// Add date, author, title and text
			title = title.Replace("%20", " ");
			text = textIn(text, true);
			imgnode.InnerXml = String.Concat("<date>", date, "</date><author>", author, "</author><title>", title, "</title><text>", text, "</text>");
			imgsnode.AppendChild(imgnode);
		}

		/// <overloads>Approve a image to a blog (ie. set "ok" attribute to "yes").</overloads>
		/// <summary>ids as strings</summary>
		public void ApproveImage(string name, string topic, string id, string image_id) {
			int blog_id = Convert.ToInt32(id);
			int blog_iid = Convert.ToInt32(image_id);
			ApproveImage(name, topic, blog_id, blog_iid);
		}
		/// <summary>ids as integers</summary>
		public void ApproveImage(string name, string topic, int id, int image_id) {
			_Load(name, topic);
			selectBlog(id);
			XmlElement imgnode = blog.SelectSingleNode("images/image[@id='" + image_id + "']") as XmlElement;
			if (imgnode != null)
				imgnode.SetAttribute("ok", "yes");
			else
				throw (new x_exception("error_imageexist", "image does not exist"));
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}

		/// <overloads>Delete a image to a blog.</overloads>
		/// <summary>ids as strings</summary>
		public void DeleteImage(string name, string topic, string id, string image_id) {
			int blog_id = Convert.ToInt32(id);
			int blog_iid = Convert.ToInt32(image_id);
			DeleteImage(name, topic, blog_id, blog_iid);
		}
		/// <summary>ids as integers</summary>
		public void DeleteImage(string name, string topic, int id, int image_id) {
			_Load(name, topic);
			selectBlog(id);
			XmlNode imgnode = blog.SelectSingleNode("images/image[@id='" + image_id + "']");
			if (imgnode != null)
				imgnode.ParentNode.RemoveChild(imgnode);
			else
				throw (new x_exception("error_imageexist", "image does not exist"));
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}

		/// <summary>
		/// Edit a image to a blog.
		/// </summary>
		public void EditImage(string name, string topic, int id, int image_id, string title, string text) {
			_Load(name, topic);
			selectBlog(id);
			XmlElement imgnode = (XmlElement)blog.SelectSingleNode("images/image[@id='" + image_id + "']");
			if (imgnode == null)
				throw (new x_exception("error_imageexist", "image does not exist"));
			// set date attribute and descriptive date element
			string date = setDate(imgnode);
			// Add title and text
			imgnode.SelectSingleNode("date").InnerXml = date;
			imgnode.SelectSingleNode("title").InnerXml = title.Replace("%20", " "); ;
			imgnode.SelectSingleNode("text").InnerXml = textIn(text);
			_blogs.Save(pathBlog(name, topic));
			updateSpace(name, topic);
		}

		/// <summary>Set a blogspace to be default.</summary>
		public void DefaultImage(string name, string topic, int id, int image_id) {
			_Load(name, topic);
			selectBlog(id);
			XmlNode imgnode = blog.SelectSingleNode("images/image[@id='" + image_id + "']");
			if (imgnode == null)
				throw (new x_exception("error_imageexist", "image does not exist"));
			else {
				XmlElement imgsnode = blog.SelectSingleNode("images") as XmlElement;
				imgsnode.SetAttribute("default", image_id.ToString());
				_blogs.Save(pathBlog(name, topic));
			}
		}
		
		
		/// <summary>
		/// Counts the number of blogs.
		/// </summary>
		public int Count(string name, string topic) {
			_Load(name, topic);
			return (Blogs.SelectNodes("//blogs/blog").Count);
		}

		/// <summary>
		/// Decodes blog elements for correct display in html / use in xsl. Assumes the blog is loaded
		/// </summary>
		public void Decode() {
			// the following is for backward compatability
			foreach (XmlNode textnode in blog.SelectNodes("//blog/text")) {
				XmlElement textel = (XmlElement)textnode;
				textel.SetAttribute("decoded", "yes");
				textel.InnerXml = (textel.GetAttribute("type") == "html" || textel.GetAttribute("html") == "yes") ? HttpUtility.HtmlDecode(textel.InnerText) : HttpUtility.HtmlDecode(textel.InnerXml);
			}
			foreach (XmlNode textnode in blog.SelectNodes("//blog/bloghtml")) {
				XmlElement textel = (XmlElement)textnode;
				textel.SetAttribute("decoded", "yes");
				textel.InnerXml = HttpUtility.HtmlDecode(textel.InnerText);
			}
			foreach (XmlNode textnode in blog.SelectNodes("//blog/blogtext")) {
				XmlElement textel = (XmlElement)textnode;
				textel.SetAttribute("decoded", "yes");
				textel.InnerXml = HttpUtility.HtmlDecode(textel.InnerXml);
			}
			foreach (XmlNode textnode in blog.SelectNodes("//blog/*[@type='html']")) {
				XmlElement textel = (XmlElement)textnode;
				textel.SetAttribute("decoded", "yes");
				textel.InnerXml = HttpUtility.HtmlDecode(textel.InnerText);
			}
		}
		#endregion

		#region Protected methods
		/// <overloads>Load the blog space.</overloads>
		/// <summary>Current topic</summary>
		protected void _Load(string name, string topic) {
			_name = name;
			_topic = topic;
			checkSpace(name);
			getConfig(name);
			checkTopic(topic);
			getBlogs(name, topic);
		}
		/// <summary>Topic and archive</summary>
		protected void _Load(string name, string topic, string which) {
			_name = name;
			_topic = topic;
			_id = which;
			checkSpace(name);
			getConfig(name);
			checkTopic(topic);	// archives may exist for topics that have been deleted
			getBlogs(name, topic, which);
		}
		#endregion

		#region Private methods
		private void initialise(HttpApplicationState app) {
			_application = app;
			app.Lock();
			_root = (XmlDocument) app["BlogX"];
			_path = (string) app["DataPathX"];
			app.UnLock();
		}
		private void checkSpace(string name) {
			XmlElement blogspace = (XmlElement) _root.SelectSingleNode("//blogspaces/blogspace[@name='"+name+"']");
			if (blogspace == null)
				throw(new x_exception("error_spaceexist", String.Concat("blogspace '", name, "'does not exist")));
			if (blogspace.GetAttribute("active") != "yes")
				throw(new x_exception("error_inactive", "blogspace is not active"));
		}
		private void checkTopic(string name) {
			XmlElement topicel = (XmlElement) _config.SelectSingleNode("//topics/topic[@name='"+name+"']");
			if (topicel == null)
				throw(new x_exception("error_topicexist", String.Concat("blogspace topic '", name,"' does not exist")));
		}
		private void getConfig(string name) {
			_config = new XmlDocument();
			_config.Load(pathConfig(name));
		}
		private void getBlogs() {
			_blogs = new XmlDocument();
			_blogs.LoadXml("<blogX><blogs/></blogX>");
		}
		private void getBlogs(string path) {
			_blogs = new XmlDocument();
			try {
				_blogs.Load(path);
			} catch (System.Exception e) {
				throw(new x_exception("error_loadblog", String.Concat("blog not loaded (", e.Message, ")")));
			}
		}
		private void getBlogs(string name, string topic) {
			_blogs = new XmlDocument();
			_blogs.Load(pathBlog(name, topic));
		}
		private void getBlogs(string name, string topic, string archive) {
			_blogs = new XmlDocument();
			_blogs.Load(pathArchive(name, topic, archive));
		}
		private void selectBlog(int id) {
			selectBlog(id.ToString());
		}
		private void selectBlog(string id) {
			if (_blogs == null)
				throw(new x_exception("error_loadspace", "blogspace not loaded"));
			if (id != null && id != "")
				blog = (XmlElement) _blogs.SelectSingleNode("//blogs/blog[@id='"+id.ToString()+"']");
			else
				blog = (XmlElement) _blogs.SelectSingleNode("//blogs/blog[last()]");
			if (blog == null)
				throw(new x_exception("error_loadblog", String.Concat("blog not loaded:", id.ToString(), "[name:", _name, " topic:", _topic, "]")));
		}
		private void removeBlogs(string ids) {
			char[] sep = new Char[] {',', '|', ';', ':', ' '};
			if (_blogs == null)
				throw(new x_exception("error_loadspace", "blogspace not loaded"));
			if (ids != "") {
				string blogxml = "";
				string[] blogids = ids.Split(sep);
				int thisid, count = _blogs.SelectNodes("//blogs/blog").Count;
				foreach (string id in blogids) {
					thisid = System.Convert.ToInt32(id);
					thisid = (thisid < 0)? count+1+thisid: thisid;
					XmlNode thisblog = _blogs.SelectSingleNode("//blogs/blog[@id='"+thisid.ToString()+"']");
					if (thisblog != null)
						blogxml += thisblog.OuterXml;
				}
				_blogs.SelectSingleNode("//blogs").InnerXml = blogxml;
			}
		}
		private void removeBlogs(int id) {
			if (_blogs == null)
				throw(new x_exception("error_loadspace", "blogspace not loaded"));
			if (id != 0) {
				int count = _blogs.SelectNodes("//blogs/blog").Count;
				int thisid = (id < 0)? count+1+id: id;
				XmlNode thisblog = _blogs.SelectSingleNode("//blogs/blog[@id='"+thisid.ToString()+"']");
				if (thisblog != null)
					_blogs.DocumentElement.AppendChild(thisblog);
				_blogs.DocumentElement.RemoveChild(_blogs.SelectSingleNode("//blogs"));
			}
		}
		private void addTopics() {
			XmlElement el = _blogs.CreateElement("topics");
			el.InnerXml = _config.SelectSingleNode("//topics").InnerXml;
			_blogs.DocumentElement.InsertBefore(el, _blogs.DocumentElement.FirstChild);
		}
		private void addArchives() {
			XmlElement el = _blogs.CreateElement("archives");
			el.InnerXml = _config.SelectSingleNode("//archives").InnerXml;
			_blogs.DocumentElement.InsertBefore(el, _blogs.DocumentElement.FirstChild);
		}
		private bool checkArchive(string type) { // determine if needs to be archived by checking dates
			DateTime now = DateTime.Now;
			DateTime then = Date;
			bool doit = false;
			switch (type) {
				case "unique":	doit = true;	break;
				case "daily":	doit = (now.Subtract(then).TotalHours > 24.0);	break;
				case "weekly":	doit = (now.Subtract(then).TotalDays > 7.0);	break;
				case "monthly":	doit = (now.Month - then.Month > 0);	break;
				case "never":
				default:		doit = false;	break;
			}
			return(doit);
		}
		private string getArchiveName(string type) {
			DateTime now = DateTime.Now;
			string archstr = now.Year.ToString();
			switch (type) {
				case "unique":	archstr += now.Month.ToString("00") + now.Day.ToString("00") + now.TimeOfDay.TotalHours.ToString("00.00");	break;
				case "daily":	archstr += now.Month.ToString("00") + now.Day.ToString("00");	break;
				case "weekly":	archstr += now.Month.ToString("00") + now.Day.ToString("00");	break;
				case "monthly":	archstr += now.Month.ToString("00");	break;
			}
			return(archstr);
		}
		private string pathConfig(string name) { return _path + "/Blogs/" + name + "/blogx.xml"; }
		private string pathBlog(string name, string topic) { return _path + "/Blogs/" + name + "/" + topic + ".xml"; }
		private string pathArchive(string name, string topic, string archive) { return _path + "/Blogs/" + name + "/archive/" + topic + "_" + archive + ".xml"; }
		/// <overloads>Sets the date on a blog and returns the date as a descriptive date string</overloads>
		/// <summary>Use the current date-time (i.e now)</summary>
		private string setDate(XmlElement elem) {
			return setDate(elem, DateTime.Now);
		}
		/// <summary>Use a date string</summary>
		private string setDate(XmlElement elem, string date) {
			elem.SetAttribute("date", date);
			setCreateDate(elem);
			return date;
		}
		/// <summary>Use a specified date-time (i.e an object)</summary>
		private string setDate(XmlElement elem, DateTime now) {
			elem.SetAttribute("date", now.ToString("s", DateTimeFormatInfo.InvariantInfo));
			setCreateDate(elem);
			return(now.ToLongDateString());
		}
		/// <summary>bring older blogs into line where 'date' element has a 'create' attribute</summary>
		private void setCreateDate(XmlElement elem) {
			XmlElement dateelem = (XmlElement)elem.SelectSingleNode("date");
			if (dateelem != null) {	// only if exists - for safety.
				if (dateelem.GetAttribute("create") == "") {	// if exists leave it
					DateTime createDate = DateTime.Parse(dateelem.InnerText);
					dateelem.SetAttribute("create", createDate.ToString("s", DateTimeFormatInfo.InvariantInfo));
				}
			}
		}
		private void updateSpace(string name, string topic) {
			check_blogspace(name, topic);
			_blogspace.Index();
		}
		private void updateRecent(XmlElement thisblog, string topic) {
			check_blogspace();
			_blogspace.AddRecent(thisblog, topic);
		}
		private void updateRecent(XmlElement thisblog, string topic, int id) {
			check_blogspace();
			_blogspace.AddRecent(thisblog, topic, id);
		}
		private void updateComment(XmlElement thisblog, string topic, int id) {
			check_blogspace();
			_blogspace.AddComment(thisblog, topic, id);
		}
		private void check_blogspace() {
			if (_blogspace == null)
				throw(new x_exception("error_space", "blogspace not loaded"));
		}
		private void check_blogspace(string name, string topic) {
			if (_blogspace == null)
				initBlogspace(name, topic);
		}
		private void initBlogspace(string name, string topic) {
			_blogspace = new x_blogspace(_application, name, topic);	// NB this will check archive
		}
		private string textIn(string txt) {
			//txt = txt.Replace("&amp;", "&");
			//txt = txt.Replace("&", "&amp;");
			txt = txt.Replace("\"", "'");
			txt = txt.Replace("%20", " ");
			txt = txt.Replace(Environment.NewLine, "<br/>");
			txt = txt.Replace("\n", "<br/>");
			return(txt);
		}
		private string textIn(string txt, bool amp) {
			if (amp)
				txt = txt.Replace("&", "and");	
			txt = txt.Replace("\"", "'");
			txt = txt.Replace("%20", " ");
			txt = txt.Replace(Environment.NewLine, "<br/>");
			txt = txt.Replace("\n", "<br/>");
			return(txt);
		}
		private string htmlIn(string htm) {
			htm = HttpUtility.HtmlEncode(htm);
			htm = htm.Replace("\n", "");
			return(htm.Replace("%20", " "));
		}
		private DateTime newDate(string yyyy, string mm, string dd) {
			DateTime thisdate;
			try {
				int year = System.Convert.ToInt32(yyyy);
				int month = System.Convert.ToInt32(mm);
				int day = System.Convert.ToInt32(dd);
				thisdate = new DateTime(year, month, day);
			} catch(System.Exception e) {
				//throw(new x_exception("error_date", "date not valid"));
				string msg = e.Message;
				thisdate = DateTime.Now;
			}
			return thisdate;
		}
		#endregion
	}
}
